#ifndef TEXTPREPROCESSOR_HPP
#define TEXTPREPROCESSOR_HPP

// #define INLINE_TEST 1

#include <iostream>
#include <vector>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <cstdarg>
#include <sys/time.h>
#include "Url.hpp"
#include "../hash.hpp"

#define ARRAY_SIZE(a) sizeof(a) / sizeof(a[0])
#define IS_BREAK(c) (c == ' ' || c == '\0' || c == '\a' || c == '\b' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r')
#define IS_URL_PART(url, i) \
	/* url[i] == 'h' && */ url[i + 1] == 't' && url[i + 2] == 't' && url[i + 3] == 'p' && \
	(\
		(url[i + 4] == ':' && url[i + 5] == '/' && url[i + 6] == '/') ||\
		(url[i + 4] == 's' && url[i + 5] == ':' && url[i + 6] == '/' && url[i + 7] == '/')\
	)

#define IS_URL(url, i) (\
	url[i] == 'h' && url[i + 1] == 't' && url[i + 2] == 't' && url[i + 3] == 'p' && \
	(\
		(url[i + 4] == ':' && url[i + 5] == '/' && url[i + 6] == '/') ||\
		(url[i + 4] == 's' && url[i + 5] == ':' && url[i + 6] == '/' && url[i + 7] == '/')\
	)\
)

#define IS_EXT_LINK(url, len, hurl, hlen) (\
	strncmp(hurl, url, hlen) != 0 || \
	(len > hlen && url[hlen] != '/' && url[hlen] != '?') \
)

#define MIN_USER_NAME 3
#define MAX_USER_NAME 16
#define NICK_VALIDATE(c) (    \
	c == '_' ||               \
	(c >= 'a' && c <= 'z') || \
	(c >= '0' && c <= '9') || \
	(c >= 'A' && c <= 'Z')    \
)

#define TP_STATE_NONE            0
#define TP_STATE_BBCODE_ANALIZED 1
#define TP_STATE_BBCODE_PREPARED 2
#define TP_STATE_BBCODE_SPLITTED 4

#define TPA_NONE 0
#define TPA_ESC  1
#define TPA_VAL  2
#define TPA_END  4

#define TP_BUFFER_SIZE            1024
#define TP_TAGS_POOL_RESERVE      64

#define BBCODE_BREAK      "\n"
#define BBCODE_BOLD       "b"
#define BBCODE_ITALIC     "i"
#define BBCODE_STRIKE     "s"
#define BBCODE_UNDERLINE  "u"
#define BBCODE_URL        "url"
#define BBCODE_URL_SINGLE "url"
#define BBCODE_COLOR      "color"
#define BBCODE_BGCOLOR    "bgcolor"
#define BBCODE_CODE       "code"
#define BBCODE_USER       "user"
#define BBCODE_SMILE      "smile"
#define BBCODE_SPOILER    "spoiler"

#define SPLITTER_POINTERS " ... "

#define BBCODE_FLAG_NONE          0
#define BBCODE_FLAG_REQUIRE_VALUE 1
#define BBCODE_FLAG_IS_SINGLE     2
#define BBCODE_FLAG_INNER_TAGS    4
#define BBCODE_FLAG_DISABLED      8

#define TP_FLAGS_NONE         0
#define TP_FLAGS_NO_BGCOLOR   1
#define TP_FLAGS_NO_EXT_LINKS 2
#define TP_FLAGS_NO_SMILEYS   4

#define STRCMP_CONST(str, str_const) strncmp(str_const, str, sizeof(str_const) - 1)
#define BBCODE_TAG(name, open, open2, close, flags) { \
	name, sizeof(name)   - 1, \
	open, sizeof(open)   - 1, \
	open2, sizeof(open2) - 1, \
	close, sizeof(close) - 1, \
	flags,                    \
}

#define NEW_TAG(tag, startopen, endopen, startclose, endclose) \
	tag.meta        = (BBCodeTagMetaData *)malloc(sizeof(BBCodeTagMetaData)); \
	tag.start_open  = startopen;  \
	tag.end_open    = endopen;    \
	tag.start_close = startclose; \
	tag.end_close   = endclose; 

#define NEW_TAG_META(meta, _id, _value, _value_length) \
	meta -> id           = _id;            \
	meta -> value        = _value,         \
	meta -> value_length = _value_length; 

#define BBCODE_MIN_SML_LENGTH         3
#define BBCODE_MAX_SML_LENGTH         21
#define BBCODE_MAX_TAG_LENGTH         16
#define BBCODE_MAX_VAL_LENGTH         102400
#define BBCODE_MAX_TAG_REPLACE_LENGTH 256
#define BBCODE_MIN_URL_LENGTH         11
#define BBCODE_MAX_URL_LENGTH         256
#define BBCODE_SID_LENGTH             32

using namespace std; 
class TextPreprocessor {
	private:
		#pragma pack(push, 1)
		typedef struct BBCodeTagMetaData {
			unsigned char  id; 
			char          *value; 
			unsigned short value_length; 
		} BBCodeTagMetaData; 
		
		typedef struct BBCodeTag {
			BBCodeTagMetaData *meta; 
			unsigned int start_open; 
			unsigned int end_open; 
			unsigned int start_close; 
			unsigned int end_close; 
		} BBCodeTag; 
		
		typedef struct BBCodeTagMeta {
			char   name[BBCODE_MAX_TAG_LENGTH + 1]; 
			unsigned int name_length; 
			
			char   open_tag[BBCODE_MAX_TAG_REPLACE_LENGTH + 1]; 
			unsigned int open_tag_length; 
			
			char   open_tag2[BBCODE_MAX_TAG_REPLACE_LENGTH + 1]; 
			unsigned int open_tag2_length; 
			
			char   close_tag[BBCODE_MAX_TAG_REPLACE_LENGTH + 1]; 
			unsigned int close_tag_length; 
			
			unsigned char flags; 
		} BBCodeTagMeta; 
		#pragma pack(pop)
		
		static const BBCodeTagMeta tags_meta[]; 
		
		enum {
			BBCODE_BREAK_ID = 0, 
			BBCODE_BOLD_ID, 
			BBCODE_ITALIC_ID, 
			BBCODE_STRIKE_ID, 
			BBCODE_UNDERLINE_ID, 
			BBCODE_URL_ID, 
			BBCODE_URL_SINGLE_ID, 
			BBCODE_COLOR_ID, 
			BBCODE_BGCOLOR_ID, 
			BBCODE_SPOILER_ID, 
			BBCODE_CODE_ID, 
			BBCODE_USER_ID, 
			BBCODE_SMILE_ID, 
		}; 
		
		#include "_smileys_array.hpp"
		
		static const char hex_chars[16]; 
		Url home_url, redirect_url, user_url; 
		char smileys_url[BBCODE_MAX_URL_LENGTH + 1]; 
		
		char session_id[BBCODE_SID_LENGTH + 1]; 
		unsigned short smileys_url_length; 
		
		unsigned int state; 
		vector<BBCodeTag> tags; 
		char *text; 
		unsigned int text_length;
		unsigned int text_allocated; 
		unsigned int flags; 
		int text_global_delta; 
		
		inline void replace (
			const char *tag_start, unsigned short tag_start_len, 
			const char *tag_val, unsigned short tag_val_len, 
			const char *tag_start2, unsigned short tag_start2_len, 
			const char *tag_start3, unsigned short tag_start3_len, 
			const char *tag_start4, unsigned short tag_start4_len, 
			const char *tag_start5, unsigned short tag_start5_len, 
			unsigned int start, unsigned int end, size_t tag_id
		); 
		
		char *normalizeUrl(char *str, unsigned short str_length, unsigned short *new_str_length, bool *need_to_free); 
		inline static unsigned short normalizeUrlCalculateLength(char *str, unsigned short str_length); 
		
		char *prepareRedirectUrl(char *str, unsigned short str_length, unsigned short *new_str_length, bool *need_to_free, bool wrap); 
		inline static unsigned short _prepareRedirectUrlCalculateLength(char *str, unsigned short str_length, bool wrap); 
		
		inline static unsigned int findUnicodeStart(char *text, unsigned int pos); 
		inline static unsigned int findUnicodeEnd(char *text, unsigned int pos, unsigned int len); 
		
		inline static void freeTagData(BBCodeTag &tag) {
			if (tag.meta) {
				if (tag.meta -> value)
					::free(tag.meta -> value); 
				::free(tag.meta); 
				tag.meta = NULL; 
			}
		}
		
		inline static void freeTags(vector<BBCodeTag> &bbtags) {
			for (size_t i = 0; i < bbtags.size(); i++)
				freeTagData(bbtags[i]); 
			bbtags.clear(); 
		}
		
		inline static void deleteTag(vector<BBCodeTag> &bbtags, size_t index) {
			freeTagData(bbtags[index]); 
			bbtags.erase(bbtags.begin() + index); 
		}
		
		inline static short smileToId(const char *smile); 
		inline static char tagToId(const char *tag, bool required_value); 
		inline static bool isValidColor(const char *color); 
	public: 
		TextPreprocessor() {
			text = NULL; 
			smileys_url_length = 0; 
			this -> state = TP_STATE_NONE; 
			this -> flags = TP_FLAGS_NONE; 
			strcpy(this -> session_id, ""); 
		}
		
		inline void setText(const char *text, unsigned int length) {
			this -> free(); 
			this -> text = (char *)malloc(length + 1); 
			memcpy(this -> text, text, length); 
			this -> text_global_delta = 0; 
			this -> text_allocated = length + 1; 
			this -> text_length    = length; 
			this -> text[this -> text_length] = 0; 
		}
		
		const inline char *getText() {
			return text; 
		}
		
		inline const unsigned int getLength() {
			return text_length; 
		}
		
		inline void setSessionId(const char *sid, unsigned int sid_length) {
			this -> session_id[0] = 0; 
			if (sid_length != BBCODE_SID_LENGTH)
				return; 
			memcpy(this -> session_id, sid, sid_length); 
			this -> session_id[BBCODE_SID_LENGTH] = 0; 
		}
		
		inline void setHomeUrl(Url &url) {
			this -> home_url.copy(url); 
		}
		
		inline void setRedirectUrl(Url &url) {
			this -> redirect_url.copy(url); 
		}
		
		inline void setUserUrl(Url &url) {
			this -> user_url.copy(url); 
		}
		
		inline void setHomeUrl(const char *home_url, unsigned int home_url_length) {
			this -> home_url.parse(home_url, home_url_length); 
		}
		
		inline void setRedirectUrl(const char *redirect_url, unsigned int redirect_url_length) {
			this -> redirect_url.parse(redirect_url, redirect_url_length); 
		}
		
		inline void setUserUrl(const char *user_url, unsigned int user_url_length) {
			this -> user_url.parse(user_url, user_url_length); 
		}
		
		inline void setSmileysUrl(const char *smileys_url, unsigned int smileys_url_length) {
			if (smileys_url_length > BBCODE_MAX_URL_LENGTH)
				smileys_url_length = BBCODE_MAX_URL_LENGTH; 
			memcpy(this -> smileys_url, smileys_url, smileys_url_length); 
			this -> smileys_url_length = smileys_url_length; 
		}
		
		inline void setUrls(
			const char *home_url,     unsigned int home_url_length, 
			const char *redirect_url, unsigned int redirect_url_length, 
			const char *user_url,     unsigned int user_url_length, 
			const char *smileys_url,  unsigned int smileys_url_length
		) {
			setHomeUrl     (home_url,     home_url_length); 
			setRedirectUrl (redirect_url, redirect_url_length); 
			setUserUrl     (user_url,     user_url_length); 
			setSmileysUrl  (smileys_url,  smileys_url_length); 
		}
		
		inline void setFlags(unsigned int flags) {
			this -> flags = flags; 
		}
		
		void analize(); 
		void prepare(); 
		void resize(unsigned int start, unsigned int size, bool pointers = false); 
		inline void free() {
			freeTags(tags); 
			vector<BBCodeTag>().swap(tags); 
			this -> state = TP_STATE_NONE; 
			if (text) {
				::free(text); 
				text = 0; 
			}
		}
		
		~TextPreprocessor() {
			this -> free(); 
		}
}; 

#endif // TEXTPREPROCESSOR_HPP
